#include "vector.h"

static inline void decrease(losu_vm_t vm, VectorHeader *h, losu_object_t o) {
    RefDecrease(vm, h->ref, o, 1);
}

static inline void increase(losu_vm_t vm, VectorHeader *h, losu_object_t o) {
    RefIncrease(vm, h->ref, o, 1);
}

losu_extern_t VectorHeader *VectorInit(losu_vm_t vm) {
    size_t len = sizeof(losu_object) * DEFAULT_LIST_LENGTH;
    VectorHeader *h = (VectorHeader *)__xkit_malloc(sizeof(VectorHeader));
    h->val = (losu_object *)__xkit_malloc(len);
    h->curIndex = 0;
    h->realLength = DEFAULT_LIST_LENGTH;
    h->ref = RefInit(vm);
    return h;
}

losu_extern_t void VectorFree(losu_vm_t vm, VectorHeader *p) {
    RefFree(vm, p->ref);
    __xkit_free(p->val);
    p->realLength = 0;
    p->curIndex = 0;
    __xkit_free(p);
}

losu_extern_t void VectorReserve(losu_vm_t vm, VectorHeader *p, size_t expectSize) {
    (void)vm;
    if (p->realLength >= expectSize) {
        return;
    }
    p->val = __xkit_realloc(p->val, expectSize * sizeof(losu_object));
    p->realLength = expectSize;
}

losu_extern_t void VectorAppend(losu_vm_t vm, VectorHeader *p, losu_object_t o) {
    if (p->curIndex == p->realLength) {
        p->realLength = p->realLength * 2;
        p->val = __xkit_realloc(p->val, sizeof(losu_object) * p->realLength);
    }
    p->val[p->curIndex] = *o;
    p->curIndex = p->curIndex + 1;
    increase(vm, p, o);
}

losu_extern_t bool VectorPop(losu_vm_t vm, VectorHeader *p, size_t index, losu_object_t target) {
    if (index >= p->curIndex) {
        return false;
    }
    losu_object output = p->val[index];
    decrease(vm, p, &output);
    for (size_t i = index+1; i < p->curIndex; i ++) {
        p->val[i-1] = p->val[i];
    }
    p->curIndex--;
    *target = output;
    return true;
}

losu_extern_t bool VecotrInsert(losu_vm_t vm, VectorHeader *p, size_t index, losu_object_t o) {
    if (index > p->curIndex) {
        return false;
    }

    increase(vm, p, o);

    if (p->curIndex == p->realLength) {
        p->realLength = p->realLength * 2;
        p->val = __xkit_realloc(p->val, sizeof(losu_object) * p->realLength);
    }

    for (size_t i = p->curIndex; i > index; i--) {
        p->val[i] = p->val[i-1];
    }

    p->val[index] = *o;
    p->curIndex++;

    return true;
}

losu_extern_t bool VecotrReplace(losu_vm_t vm, VectorHeader *p, size_t index, losu_object_t o) {
    if (index >= p->curIndex) {
        return false;
    }
    decrease(vm, p, p->val + index);
    increase(vm, p, o);
    p->val[index] = *o;
    return true;
}

losu_extern_t void VectorExtend(losu_vm_t vm, VectorHeader *p, VectorHeader *other) {
    size_t requiredLength = p->curIndex + other->curIndex;

    // make sure memory enough
    if (requiredLength > p->realLength) {
        size_t newLength = p->realLength;
        while (newLength < requiredLength) {
            newLength *= 2;
        }
        p->realLength = newLength;
        p->val = __xkit_realloc(p->val, sizeof(losu_object) * p->realLength);
    }

    for (size_t i = 0; i < other->curIndex; i++) {
        losu_object_t o = other->val + i;
        increase(vm, p, o);
        p->val[p->curIndex + i] = *o;
    }

    p->curIndex = requiredLength;
}

losu_extern_t bool VectorGet(losu_vm_t vm, VectorHeader *p, size_t index, losu_object_t target) {
    (void)vm;
    if (index >= p->curIndex) {
        return false;
    }

    *target = p->val[index];
    return true;
}

losu_extern_t void VectorClear(losu_vm_t vm, VectorHeader *p) {
    RefDecrease(vm, p->ref, p->val, p->curIndex);
    p->curIndex = 0;
}

static size_t max(size_t a, size_t b) {
    return a >= b ? a : b;
}

losu_extern_t void VectorTrim(losu_vm_t vm, VectorHeader *p) {
    (void)vm;
    size_t newLen = max(DEFAULT_LIST_LENGTH, p->curIndex);
    p->val = __xkit_realloc(p->val, newLen * sizeof(losu_object));
    p->realLength = newLen;
}

losu_extern_t VectorHeader *VectorSlice(losu_vm_t vm, VectorHeader *p, size_t start, size_t end) {
    VectorHeader *out = VectorInit(vm);
    if (start < end) {
        losu_object *front, *tear;
        front = p->val + start;
        tear = p->val + end;
        while (front < tear) {
            VectorAppend(vm, out, front);
            front ++;
        }
    }
    return out;
}
